package org.shiro.config;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.omg.CORBA.TIMEOUT;
import org.shiro.entity.User;
import org.shiro.filter.JwtFilter;
import org.shiro.filter.PermsFilter;
import org.shiro.filter.RoleFilter;
import org.shiro.realm.ShiroRealm;
import org.shiro.session.ShiroSessionIdGenerator;
import org.shiro.session.ShiroSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.*;

/** @author LENOVO */
@Configuration
public class ShiroConfig {
  private static final String CACHE_KEY = "shiro:cache";

  private static final String SESSION_KEY = "shiro:session";

  private static final String HOST = "127.0.0.1";

  private static final int PORT = 6379;

  private static final int TIMEOUT = 5000;

  private static final int DATASOURCE = 0;

  /**
   * 开启shiro-aop注解支持@attention使用代理方式，需要开启代码支持
   *
   * @param securityManager
   * @return
   */
  @Bean
  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
      SecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor auth = new AuthorizationAttributeSourceAdvisor();
    auth.setSecurityManager(securityManager);
    return auth;
  }

  /**
   * shiro基础配置
   *
   * @param securityManager
   * @return
   */
  @Bean
  public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {
    ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean();
    factory.setSecurityManager(securityManager);
    Map<String, Filter> filterMap = new LinkedHashMap<>();
    filterMap.put("jwt", new JwtFilter());
    filterMap.put("roles", new RoleFilter());
    filterMap.put("perms", new PermsFilter());
    factory.setFilters(filterMap);
    Map<String, String> filterChain = new LinkedHashMap<>();
    // 注意过滤器配置顺序不能颠倒，配置过滤:不会被拦截的链接 ，anon不需验证，authc需验证
    filterChain.put("/swagger-resources/configuration/ui", "anon");
    filterChain.put("/swagger-resources", "anon");
    filterChain.put("/swagger-resources/configuration/security", "anon");
    filterChain.put("/webjars/springfox-swagger-ui/**", "anon");
    filterChain.put("/v2/**", "anon");
    filterChain.put("/swagger-ui.html", "anon");
    filterChain.put("/doc.html", "anon");
    filterChain.put("/static/**", "anon");
    filterChain.put("/user/login/**", "anon");
    filterChain.put("/user/update*", "roles[admin]");
    filterChain.put("/user/update*", "perms[sys:user:update]");
    filterChain.put("/**", "jwt");
    filterChain.put("/logout", "logout");
    // 配置shiro默认登录界面地址，前后端分离中登录界面跳转应由前端路由控制，后台仅返回json数据
    factory.setLoginUrl("/auth/unLogin");
    // 登录成功后要跳转的链接
    factory.setSuccessUrl("/index");
    //  未授权页面
    factory.setUnauthorizedUrl("/401");
    factory.setFilterChainDefinitionMap(filterChain);
    return factory;
  }
  /** 安全管理器 */
  @Bean
  public DefaultWebSecurityManager securityManager() {
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    List<Realm> collects = new LinkedList<>();
    collects.add(shiroRealm());
    manager.setRealms(collects);
    // 自定义Session管理
    manager.setSessionManager(sessionManager());
    // 自定义Cache实现
    // manager.setCacheManager(cacheManager());
    // 自定义realm实现
    manager.setRealm(shiroRealm());
    return manager;
  }

  /** 身份验证器 */
  @Bean(name = "shiroRealm")
  public ShiroRealm shiroRealm() {
    ShiroRealm shiroRealm = new ShiroRealm();
    shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
    shiroRealm.setCachingEnabled(true);
    shiroRealm.setAuthorizationCachingEnabled(true);
    shiroRealm.setAuthenticationCachingEnabled(true);
    return shiroRealm;
  }
  /** 凭证匹配器 将密码校验交给Shiro的SimpleAuthenticationInfo进行处理,在这里做匹配配置 */
  @Bean(name = "hashedCredentialsMatcher")
  public HashedCredentialsMatcher hashedCredentialsMatcher() {
    HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
    // 散列算法:这里使用md5算法;
    matcher.setHashAlgorithmName(Md5Hash.ALGORITHM_NAME);
    // 散列的次数，比如散列两次，相当于 md5(md5(""));
    matcher.setHashIterations(5);
    // 数据库存储的密码字段使用HEX还是BASE64方式加密
    matcher.setStoredCredentialsHexEncoded(true);
    return matcher;
  }
  /** 配置Redis管理器 @Attention 使用的是shiro-redis开源插件 */
  @Bean(name = "redisManager")
  public RedisManager redisManager() {
    RedisManager redis = new RedisManager();
    redis.setHost(HOST);
    redis.setPort(PORT);
    redis.setTimeout(TIMEOUT);
    redis.setDatabase(DATASOURCE);
    return redis;
  }
  /** 配置Cache管理器 用于往Redis存储权限和角色标识 @Attention 使用的是shiro-redis开源插件 */
  @Bean(name = "redisCacheManager")
  public RedisCacheManager cacheManager() {
    RedisCacheManager manager = new RedisCacheManager();
    manager.setRedisManager(redisManager());
    manager.setKeyPrefix(CACHE_KEY);
    // 配置缓存的话要求放在session里面的实体类必须有个id标识，这里使用用户Id
    manager.setPrincipalIdFieldName(UUID.randomUUID().toString());
    return manager;
  }
  /** SessionID生成器 */
  @Bean
  public ShiroSessionIdGenerator sessionIdGenerator() {
    return new ShiroSessionIdGenerator();
  }

  /** 配置RedisSessionDAO @Attention 使用的是shiro-redis开源插件 */
  @Bean
  public RedisSessionDAO redisSessionDAO() {
    RedisSessionDAO red = new RedisSessionDAO();
    red.setRedisManager(redisManager());
    red.setSessionIdGenerator(sessionIdGenerator());
    red.setKeyPrefix(SESSION_KEY);
    red.setExpire(TIMEOUT);
    return red;
  }

  /** 配置Session管理器 */
  @Bean
  public SessionManager sessionManager() {
    ShiroSessionManager manager = new ShiroSessionManager();
    manager.setSessionDAO(redisSessionDAO());
    return manager;
  }

  /**
   * 开启注解 作用：用来扫描上下文寻找所有的Advisor（通知器）符合条件的Advisor应用到切入点的bean中 需要在 LifecycleBeanPostProcessor创建后才可以创建
   */
  @Bean
  public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
    //    强制使用cg-lib代理，防止和aop冲突
    creator.setProxyTargetClass(true);
    return creator;
  }

  /**
   * shiro生命周期 作用：管理shiro-bean的生命周期，即bean的初始化与销毁
   *
   * @return
   */
  @Bean
  public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
  }

  /**
   * 记住我
   *
   * @return
   */
  @Bean(name = "cookieRememberMeManager")
  public CookieRememberMeManager cookieRememberMeManager() {
    CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
    cookieRememberMeManager.setCookie(simpleCookie());
    return cookieRememberMeManager;
  }

  /**
   * 记住我密码实现
   *
   * @return
   */
  @Bean(name = "simpleCookie")
  public SimpleCookie simpleCookie() {
    // 这个参数是cookie的名称，对应前段的checkbox的name = rememberMe
    SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
    // cookie记住我，生效时间，7天，单位秒
    simpleCookie.setMaxAge(604800);
    return simpleCookie;
  }
}
